package net.homeip.jcperera.nu.core.msg;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.ResourceBundle;

import javax.faces.application.FacesMessage;
import javax.faces.application.FacesMessage.Severity;
import javax.faces.context.FacesContext;

import net.homeip.jcperera.nu.core.CoreBaseInterface;
import net.homeip.jcperera.nu.core.boot.Boot;
import net.homeip.jcperera.nu.core.config.Configuration;

import com.bea.logging.LogLevel;

/**
 * Encapsulates the internal messaging and exception base from which the other specific exception inherits
 * 
 * @name BaseMessage class
 * @author julio.c.perera@gmail.com
 * @application nu
 * @since 0.1.0.0
 */
public abstract class BaseMessage extends Exception implements CoreBaseInterface
{
    // Required to implement Serializable interface
    private static final long         serialVersionUID       = -564453775237316670L;

    // CoreBase properties, for easy use from other components, as well as logging
    public static final int           runtimeLevel           = 0;
    public static final String        moduleName             = "core";
    public static final String        componentName          = "nu_core_msg_base_message";

    // Get the log
    private final static Logger       log                    = Logger.getLogger();

    public static final String        SEVERITY_INFO          = "info";
    public static final String        SEVERITY_WARNING       = "warning";
    public static final String        SEVERITY_ERROR         = "error";
    private String                    severity               = SEVERITY_ERROR;

    public static final String        FRIENDLINESS_FRIENDLY  = "friendly";
    public static final String        FRIENDLINESS_TECHNICAL = "technical";
    private String                    friendliness           = FRIENDLINESS_TECHNICAL;

    // Reference to Configuration
    private Configuration             configuration;

    // TODO: Implement messaging using other resource bundles (from database)
    private static ResourceBundle     baseResourceBundle;

    /**
     * <p>
     * The <code>code</code> of the BaseMessage, for retrieving the Message's description, descriptions to codes should
     * be specified in the associated resource bundle for internationalization, the <code>code</code> of the BaseMessage
     * is interpreted as the resource key and the description is the resource value.
     * </p>
     * <p>
     * Example: <code>my.error.message.code=Invalid operation, please try again</code>
     * </p>
     */
    private String                    code                   = "";

    /**
     * <p>
     * The <code>extensions</code> for the Description of the BaseMessage, as a <code>Hashtable</code> where the keys
     * are the parse points (are looked for replacement using {@link String#replaceAll(String, String)}), and the
     * replacements are the values.
     * </p>
     * <p>
     * This way, you can insert values at runtime using any syntax you like for replacement values, <b>taking of course
     * special care as the key is interpreted as a regular expression</b>.
     * </p>
     * <p>
     * <b>Example:</b><br/>
     * <code>my.error.message.code=User '%user%' is not allowed access to resource '%resource%'.</code><br/>
     * <br/>
     * using the following <code>extensions</code> readed as <i>key</i> - <i>value</i>:<br/>
     * <code>%user% - johndoe</code><br/>
     * <code>%resource% - /usr/home/root/private.resource</code><br/>
     * <br/>
     * Would print the following message:<br/>
     * <code>User 'johndoe' is not allowed access to resource '/usr/home/root/private.resource'.</code>
     * </p>
     */
    private Hashtable<String, String> extensions;

    /**
     * Constructs a BaseMessage given the <code>code</code>, <code>severity</code> and <code>friendlyness</code>
     * specified
     * 
     * @param code
     * @param severity
     * @param friendlyness
     * @param extensions
     */
    BaseMessage(String code, String severity, String friendlyness, Hashtable<String, String> extensions)
    {
        super();

        // First, we get the configuration
        configuration = Boot.getSystem().getConfiguration();
        if (configuration == null) log.error("Configuration is NULL");
        else log.info("Configuration is OK, continuing");

        setCode(code);
        setSeverity(severity);
        setFriendliness(friendlyness);
        setExtensions(extensions);

        if (baseResourceBundle == null)
        {
            String baseName = configuration.get("base.messages.location");
            log.info("BaseName for 'base.messages.location':" + baseName);
            try
            {
                baseResourceBundle = ResourceBundle.getBundle(baseName);
            }
            catch (Exception exception)
            {
                baseResourceBundle = null;
                baseName = exception.getLocalizedMessage();
            }
            ;
            if (baseResourceBundle == null) log.info("BaseResourceBundle is null, reason: '" + baseName + "'");
            else log.info("BaseResourceBundle loaded, keys =" + baseResourceBundle.keySet().size());
        }
    }

    /**
     * Constructs a BaseMessage given the <code>code</code>, <code>severity</code> and <code>friendlyness</code>
     * specified and no <code>extensions</code>, you can use {@link #setExtensions(Hashtable)} to define extensions
     * later on.
     * 
     * @param code
     * @param severity
     * @param friendlyness
     */
    BaseMessage(String code, String severity, String friendlyness)
    {
        this(code, severity, friendlyness, null);
    }

    /**
     * Constructs a BaseMessage given the <code>code</code> and <code>severity</code>, <code>friendlyness</code> at
     * default value and no <code>extensions</code>, you can use {@link #setExtensions(Hashtable)} to define extensions
     * later on.
     * 
     * @param code
     * @param severity
     */
    BaseMessage(String code, String severity)
    {
        this(code, severity, FRIENDLINESS_TECHNICAL, null);
    }

    /**
     * Constructs a BaseMessage with the <code>code</code> specified, <code>severity</code> and
     * <code>friendliness</code> at default values and no <code>extensions</code>, you can use
     * {@link #setExtensions(Hashtable)} to define extensions later on.
     * 
     * @param code
     */
    BaseMessage(String code)
    {
        this(code, SEVERITY_ERROR, FRIENDLINESS_TECHNICAL, null);
    }

    /**
     * <p>
     * Constructs an empty Message.
     * </p>
     * <p>
     * Note that you should AT LEAST assign <code>code</code> manually using {@linkplain #setCode(String)}
     * </p>
     */
    public BaseMessage()
    {
        this("", SEVERITY_ERROR, FRIENDLINESS_TECHNICAL, null);
    };

    /**
     * Investigates if the current Message should cause operation abort, given the <code>severity</code> of the Message
     * 
     * @return <code>true</code> if <code>severity</code> of this Message justifies aborting the operation
     */
    public boolean isAbortCause()
    {
        return (this.severity.equals("error"));
    }

    /**
     * Returns <code>true</code> if the passed in Message is identical to this Message.
     * 
     * @param obj
     *            The object to compare
     * @return <code>true</code> if the compared objects is identical to this object.
     * @see java.lang.Object#equals(Object)
     */
    @Override
    public boolean equals(Object obj)
    {
        if (this == obj) return true;
        if (obj == null) return false;
        if (getClass() != obj.getClass()) return false;
        final BaseMessage other = (BaseMessage) obj;
        if (this.code == null)
        {
            if (other.code != null) return false;
        }
        else if (!this.code.equals(other.code)) return false;
        if (this.friendliness == null)
        {
            if (other.friendliness != null) return false;
        }
        else if (!this.friendliness.equals(other.friendliness)) return false;
        if (this.severity == null)
        {
            if (other.severity != null) return false;
        }
        else if (!this.severity.equals(other.severity)) return false;
        return true;
    }

    public String getSeverity()
    {
        return this.severity;
    }

    /**
     * @param severity
     *            severity
     */
    protected void setSeverity(String severity)
    {
        severity = severity.trim().toLowerCase();
        if (severity.length() > 0) this.severity = severity;
    }

    public String getFriendliness()
    {
        return this.friendliness;
    }

    /**
     * @param friendlyness
     *            Friendliness of this Message
     */
    public void setFriendliness(String friendliness)
    {
        friendliness = friendliness.trim().toLowerCase();
        if (friendliness.length() > 0) this.friendliness = friendliness;
    }

    public String getCode()
    {
        return this.code;
    }

    /**
     * @param code
     *            code
     */
    public void setCode(String code)
    {
        code = code.trim().toLowerCase();
        if (code.length() > 0) this.code = code;
    }

    public void setExtensions(Hashtable<String, String> extensions)
    {
        if (extensions == null) this.extensions = new Hashtable<String, String>();
        else this.extensions = extensions;
    }

    /**
     * <p>
     * Gets the Description of the Message given the <code>code</code>, properly internationalized if defined in the
     * associated resource bundle.
     * </p>
     * <p>
     * In case of code collision, between application and base, application messages takes priority over base messages.
     * </p>
     * 
     * @return The Description of the Message associated with the <code>code</code>, parsing in the
     *         <code>extensions</code> in an appropriate fashion, or an empty string if there is no description
     *         associated to the <code>code</code>.
     */
    public String getDescription()
    {
        String result = "";
        if (this.code.length() > 0)
        {
            try
            {
                result = baseResourceBundle.getString(this.code);
            }
            catch (Exception exception)
            {
            }
        }
        if (result.length() > 0)
        {
            // Now, proceed to insert the defined extensions parsing into the
            // resulting String
            Enumeration<String> extkeys = this.extensions.keys();
            while (extkeys.hasMoreElements())
            {
                String key = extkeys.nextElement();
                String value = this.extensions.get(key);
                result = result.replaceAll(key, value);
            }
        }
        else result = "MESSAGING ERROR, specified KEY not found in the configured RESOURCE BUNDLE, this may be a Configuration or Programming Error";
        return result;
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Throwable#getMessage()
     */
    @Override
    public String getMessage()
    {
        return getDescription();
    }

    /**
     * Returns a full description of this Message.
     * 
     * @return The {@link String} with the description as text
     */
    @Override
    public String toString()
    {
        String result = "MESSAGE Code='" + getCode();
        result += "', Severity='" + getSeverity();
        result += "', Friendlyness='" + getFriendliness();
        result += "', Description='" + getDescription() + "'";
        return result;
    }

    /**
     * Generates a Textual description of this {@link BaseMessage} and depending on the severity, logs it on the proper
     * category under the Log
     */
    public void log()
    {
        if (getSeverity().equals(SEVERITY_ERROR)) log.error(toString());
        else if (getSeverity().equals(SEVERITY_WARNING)) log.warn(toString());
        else if (getSeverity().equals(SEVERITY_INFO)) log.info(toString());
    }

    /**
     * For compliance from CoreBaseInterface
     */
    public int getRuntimeLevel()
    {
        return runtimeLevel;
    }

    /**
     * For compliance from CoreBaseInterface
     */
    public String getModuleName()
    {
        return moduleName;
    }

    /**
     * For compliance from CoreBaseInterface
     */
    public String getComponentName()
    {
        return componentName;
    }

    /**
     * <p>
     * Gets the <code>Severity</code> associated to the log levels used in logging as specified in the following
     * conversion table:
     * <ul>
     * <li>LogLevel.INFO -> SEVERITY_INFO = "info"</li>
     * <li>LogLevel.WARNING -> SEVERITY_WARNING = "warning"</li>
     * <li>LogLevel.ERROR -> SEVERITY_ERROR = "error"</li>
     * </ul>
     * <b>Note: </b>Any other value for the <code>level</code> parameter returns <code>null</code>.
     * </p>
     * 
     * @param level
     *            The {@linkplain LogLevel} to convert to <code>Severity</code>
     * @return the <code>Severity</code> or <code>null</code>
     */
    public static String getSeverityFromLogLevel(LogLevel level)
    {
        String result = null;
        if (level.equals(LogLevel.INFO)) result = SEVERITY_INFO;
        else if (level.equals(LogLevel.WARNING)) result = SEVERITY_WARNING;
        else if (level.equals(LogLevel.ERROR)) result = SEVERITY_ERROR;
        return result;
    }

    /**
     * Gets the {@linkplain FacesMessage} severity equivalent to the severity of the current {@code BaseMessage}
     * 
     * @return The equivalent {@linkplain FacesMessage} severity
     */
    public Severity getFacesMessageSeverity()
    {
        return getFacesMessageSeverity(getSeverity());
    }

    /**
     * Gets the {@linkplain FacesMessage} severity equivalent to the severity of the {@code severity} parameter
     * 
     * @return The equivalent {@linkplain FacesMessage} severity
     */
    public Severity getFacesMessageSeverity(String severity)
    {
        severity = severity.trim();
        Severity result = FacesMessage.SEVERITY_INFO;
        if (severity.equalsIgnoreCase(SEVERITY_INFO)) result = FacesMessage.SEVERITY_INFO;
        else if (severity.equalsIgnoreCase(SEVERITY_WARNING)) result = FacesMessage.SEVERITY_WARN;
        else if (severity.equalsIgnoreCase(SEVERITY_ERROR)) result = FacesMessage.SEVERITY_ERROR;
        return result;
    }

    /**
     * Adds the Message to the FacesContext (in case you are using JSF FacesContext)
     * 
     * @param facesMessages
     *            The facesMessage queue
     */
    public void addMessageToFacesContext(FacesContext facesContext)
    {
        facesContext.addMessage(null, new FacesMessage(getFacesMessageSeverity(), getCode(), getDescription()));
    }
}
